package model.algorithms.linksMapping;

import ilog.concert.IloException;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;

import java.util.LinkedHashMap;
import java.util.List;

import model.components.links.link.Link;
import model.components.networks.network.VirtualNetwork;
import model.components.networks.network.PhysicalNetwork;
import model.components.nodes.node.Node;
import model.components.nodes.node.VirtualNode;
import model.components.nodes.node.PhysicalNode;

public class MultiCommodityFlow extends LinksMappingAlgorithm
{	
	public MultiCommodityFlow( String name )
	{
		super( name );
	}
	
	protected int[][][] linksMapping( VirtualNetwork requestNetwork, PhysicalNetwork substrateNetwork, LinkedHashMap< VirtualNode, PhysicalNode > nodesMapping, double availableBandwidth[][] )
	{
		List< Link > requestLinks = requestNetwork.getLinks(); 
		List< Node > requestNodes = requestNetwork.getNodes();
		int requestLinksNumber = requestLinks.size();
		int substrateNodesNumber = substrateNetwork.getNodes().size();
		
		int linksMapping[][][] = new int[ requestLinksNumber ][ substrateNodesNumber ][ substrateNodesNumber ];
		
		try
		{
			int firstRequestNodeNumber[] = new int[ requestLinksNumber ];
			int secondRequestNodeNumber[] = new int[ requestLinksNumber ];

			for( Link requestLink: requestLinks )
			{
				firstRequestNodeNumber[  requestLink.getNumber() ] = requestLink.getFirstNode().getNumber();
				secondRequestNodeNumber[ requestLink.getNumber() ] = requestLink.getSecondNode().getNumber();
			}
			
			int firstSubstrateNodeNumber[] = new int[ requestLinksNumber ];
			
			for( int i = 0; i < firstRequestNodeNumber.length; i++ )
				if( nodesMapping.containsKey( requestNodes.get( firstRequestNodeNumber[ i ] ) ) )
					firstSubstrateNodeNumber[ i ] = nodesMapping.get( requestNodes.get( firstRequestNodeNumber[ i ] ) ).getNumber();
			
			int secondSubstrateNodeNumber[] = new int[ requestLinksNumber ];
			
			for( int i = 0; i < secondRequestNodeNumber.length; i++ )
				if( nodesMapping.containsKey( requestNodes.get( secondRequestNodeNumber[ i ] ) ) )
					secondSubstrateNodeNumber[ i ] = nodesMapping.get( requestNodes.get( secondRequestNodeNumber[ i ] ) ).getNumber();

			IloCplex multiCommodityFlowCplex = new IloCplex();
			multiCommodityFlowCplex.setParam( IloCplex.DoubleParam.TiLim, 600 );
			multiCommodityFlowCplex.setParam( IloCplex.DoubleParam.ObjLLim, -1 * Double.MAX_VALUE );
			multiCommodityFlowCplex.setParam( IloCplex.DoubleParam.ObjULim, Double.MAX_VALUE );
			multiCommodityFlowCplex.setOut( null );
			
			IloNumVar linksMappingFunction[][][] = new IloNumVar[ requestLinksNumber ][ substrateNodesNumber ][ substrateNodesNumber ];
	
			for( int k = 0; k < requestLinksNumber; k++ )
				for( int i = 0; i < substrateNodesNumber; i++ )
					linksMappingFunction[ k ][ i ] = multiCommodityFlowCplex.numVarArray( substrateNodesNumber, 0.0, Double.MAX_VALUE );
			
			// Condition 1: Net flow a node is zero.
			
			for( int k = 0; k < requestLinksNumber; k++ )
				for( int i = 0; i < substrateNodesNumber; i++ )
				{
					IloLinearNumExpr left = multiCommodityFlowCplex.linearNumExpr();
					
					if( firstSubstrateNodeNumber[ k ] != i && secondSubstrateNodeNumber[ k ] != i )
						for( int j = 0; j < substrateNodesNumber; j++ )
						{
							left.addTerm(  1, linksMappingFunction[ k ][ i ][ j ] );
							left.addTerm( -1, linksMappingFunction[ k ][ j ][ i ] );
						}
					
					multiCommodityFlowCplex.addEq( left, 0 );
				}
			
			// Condition 2: Net flow a node is zero, except for the source node.
			
			double requestLinksBandwidth[][] = requestNetwork.getBandwidths();
			
			for( int k = 0; k < requestLinksNumber; k++ )
			{
				IloLinearNumExpr left = multiCommodityFlowCplex.linearNumExpr();
				
				for( int i = 0; i < substrateNodesNumber; i++ )
				{
					left.addTerm(  1, linksMappingFunction[ k ][ firstSubstrateNodeNumber[ k ] ][ i ] );
					left.addTerm( -1, linksMappingFunction[ k ][ i ][ firstSubstrateNodeNumber[ k ] ] );
				}
				
				double right = requestLinksBandwidth[ firstRequestNodeNumber[ k ] ][ secondRequestNodeNumber[ k ] ];
				
				multiCommodityFlowCplex.addEq( left, right );
			}
			
			// Condition 3: Net flow a node is zero, except for the sink node.
			
			for( int k = 0; k < requestLinksNumber; k++ )
			{
				IloLinearNumExpr left = multiCommodityFlowCplex.linearNumExpr();
				
				for( int i = 0; i < substrateNodesNumber; i++ )
				{
					left.addTerm(  1, linksMappingFunction[ k ][ secondSubstrateNodeNumber[ k ] ][ i ] );
					left.addTerm( -1, linksMappingFunction[ k ][ i ][ secondSubstrateNodeNumber[ k ] ] );
				}
	
				double right = -requestLinksBandwidth[ firstRequestNodeNumber[ k ] ][ secondRequestNodeNumber[ k ] ];
				
				multiCommodityFlowCplex.addEq( left, right );
			}
			
			// Condition 4: Capacity constraints.
			
			for( int i = 0; i < substrateNodesNumber; i++ )
				for( int j = 0; j < substrateNodesNumber; j++ )
				{
					IloNumExpr left = multiCommodityFlowCplex.prod( linksMappingFunction[ 0 ][ 0 ][ 0 ], 0 );
					
					for( int k = 0; k < requestLinksNumber; k++ )
					{
						IloNumExpr a = multiCommodityFlowCplex.prod( linksMappingFunction[ k ][ i ][ j ], 1 );
						IloNumExpr b = multiCommodityFlowCplex.prod( linksMappingFunction[ k ][ j ][ i ], 1 );
						IloNumExpr sum = multiCommodityFlowCplex.sum( a, b );
						left = multiCommodityFlowCplex.sum( left, sum );
					}
					
					double right = availableBandwidth[ i ][ j ];
					
					multiCommodityFlowCplex.addLe( left, right );
				}
			
			// Condition 5: Minimize cost.
			
			double distances[][] = this.distances( substrateNetwork );
			
			IloLinearNumExpr objectiveFunction = multiCommodityFlowCplex.linearNumExpr();
			
			for( int k = 0; k < requestLinksNumber; k++ )
				for( int i = 0; i < substrateNodesNumber; i++ )
					for( int j = 0; j < substrateNodesNumber; j++ )
					{
						double cost = 0;
						
						if( this.getCostMetric().equals( DISTANCE_COST ) )
							cost = distances[ i ][ j ];
						
						else if( this.getCostMetric().equals( BANDWIDTH_COST ) )
							cost = 1.0 / ( availableBandwidth[ i ][ j ] + Double.MIN_VALUE );
						
						objectiveFunction.addTerm( cost, linksMappingFunction[ k ][ i ][ j ] );
					}
			
			multiCommodityFlowCplex.addMinimize( objectiveFunction );
			
			// Get values.
			
			if( multiCommodityFlowCplex.solve() )
			{	
				for( int k = 0; k < requestLinksNumber; k++ )
					for( int i = 0; i < substrateNodesNumber; i++ )
						for( int j = 0; j < substrateNodesNumber; j++ )
							linksMapping[ k ][ i ][ j ] = (int) multiCommodityFlowCplex.getValue( linksMappingFunction[ k ][ i ][ j ] );
			}
			else
				linksMapping = null;
						
			multiCommodityFlowCplex.end();
		}
		catch( IloException e )
		{
			System.err.println( "Concert exception caught: " + e.toString() );
		}
		
		return linksMapping;
	}

}
